﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Chambills.Present.Validation
{
    /// <summary>
    /// Allows the ViewModel to deteermine if ANY binding excpetions have occurred.
    /// i.e. Binding Exceptions (invalid data i.e. strings in int field)
    /// 
    /// Add it to your XAML and bind to your Validator
    /// 
    /// <example>
    /// <Validation:BrokenBindingNotifier PropertiesWithBrokenBindings="{ValidatedBinding PropertiesWithBrokenBindings}" />
    /// </example>
    /// </summary>
    public class BrokenBindingNotifier : FrameworkElement
    {
        public BrokenBindingNotifier()
        {
            PropertiesWithBrokenBindings = new List<string>();
            var handler = new RoutedEventHandler(OnError);
            Initialized += (sender, e) => GetContainer().AddHandler(System.Windows.Controls.Validation.ErrorEvent, handler);
            Unloaded += (sender, e) => GetContainer().RemoveHandler(System.Windows.Controls.Validation.ErrorEvent, handler);
        }


        private IInputElement GetContainer()
        {
            if (Container == null)
            {
                Container = Parent as IInputElement;
            }

            if (Container == null)
            {
                throw new InvalidOperationException("Parent must be a UIElement");
            }

            return Container;
        }


        private void OnError(object sender, RoutedEventArgs e)
        {
            var args = (ValidationErrorEventArgs)e;
            if (!(args.Error.RuleInError is ExceptionValidationRule))
                return;

            if (args.Action == ValidationErrorEventAction.Added)
            {
                PropertiesWithBrokenBindings.Add(GetPropertyPath(args));
            }
            else
            {
                PropertiesWithBrokenBindings.Remove(GetPropertyPath(args));
            }
        }

        /// <summary>
        /// This is a little flaky and only for simple bindgins
        /// Don't turn on binding validation for complex bindings (unless you extend this)
        /// </summary>
        private string GetPropertyPath(ValidationErrorEventArgs args)
        {
            var binding = (BindingExpression)args.Error.BindingInError;
            return binding.ParentBinding.Path.Path;
        }


        public List<string> PropertiesWithBrokenBindings
        {
            get { return (List<string>)GetValue(PropertiesWithBrokenBindingsProperty); }
            set { SetValue(PropertiesWithBrokenBindingsProperty, value); }
        }

        public static readonly DependencyProperty PropertiesWithBrokenBindingsProperty = DependencyProperty.RegisterAttached("PropertiesWithBrokenBindings", typeof(List<string>), typeof(BrokenBindingNotifier));

        /// <summary>
        /// Defaults to the first Parent UIElement. 
        /// It will catch all validation messages inside that Container only.
        /// </summary>
        public IInputElement Container
        {
            get { return (IInputElement)GetValue(ContainerProperty); }
            set { SetValue(ContainerProperty, value); }
        }

        public static readonly DependencyProperty ContainerProperty = DependencyProperty.RegisterAttached("Container", typeof(IInputElement), typeof(BrokenBindingNotifier));

    }
}